﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.IO;

namespace VCDepend.Engine
{
  public abstract class Solution
  {
    private string m_contents;
    private string m_basePath;

    public Solution(string basePath, string contents)
    {
      m_basePath = basePath;
      m_contents = contents;
    }

    public ProjectStore GetProjects(IProjectParser parser, string solutionConfiguration)
    {
      if (!IsValidSolutionConfiguration(solutionConfiguration))
        throw new UnknownSolutionConfigurationException("'" + solutionConfiguration + "' is not a known solution configuration");

      ProjectStore store = new ProjectStore();

      const string pattern = @"^Project\(""\{8BC9CEB8\-8B4A\-11D0\-8D11\-00A0C91BC942\}""\) = "".*?"", ""(?<path>.*?)"", ""(?<guid>.*?)""";

      MatchCollection matches = Regex.Matches(m_contents, pattern, RegexOptions.Multiline);
      foreach (Match match in matches)
      {
        string projectId = match.Groups["guid"].Value;
        string path = GetAbsolutePath(match.Groups["path"].Value);
        string projectConfiguration = GetProjectConfiguration(projectId, solutionConfiguration);

        Project project = parser.ParseProject(projectId, path, projectConfiguration);

        store.Add(project);
      }

      return store;
    }

    public string[] GetDependencyGuids(string projectGuid)
    {
      const string pattern = @"\s(?<guid>\{.*?\})\s=\s.*?";

      string projectSection = ExtractProjectSection(projectGuid);
      List<string> dependencies = CollectMatches(projectSection, pattern);

      return dependencies.ToArray();
    }

    private string GetAbsolutePath(string projectPath)
    {
      return Path.GetFullPath(Path.Combine(m_basePath, projectPath));
    }

    private string GetProjectConfiguration(string projectGuid, string solutionConfiguration)
    {
      string projectConfiguration = string.Empty;

      string pattern = string.Format(@"{0}\.{1}\.ActiveCfg = (?<projcfg>.*?)\r$", Regex.Escape(projectGuid), Regex.Escape(solutionConfiguration));

      List<string> projectConfigurations = CollectMatches(m_contents, pattern);
      if (projectConfigurations.Count > 0)
        projectConfiguration = projectConfigurations[0];

      return projectConfiguration;
    }

    private bool IsValidSolutionConfiguration(string solutionConfiguration)
    {
      List<string> configurations = GetSolutionConfigurations(GetSolutionConfigurationSectionName());
      return configurations.Contains(solutionConfiguration);
    }

    private List<string> GetSolutionConfigurations(string sectionName)
    {
      const string pattern = @"^(?<config>.*?)\s=\s.*?$";

      string configurationSection = ExtractGlobalSection(sectionName);
      return CollectMatches(configurationSection, pattern);
    }

    private List<string> CollectMatches(string input, string pattern)
    {
      List<string> results = new List<string>();

      MatchCollection matches = Regex.Matches(input, pattern, RegexOptions.Multiline);
      foreach (Match match in matches)
      {
        results.Add(match.Groups[1].Value);
      }

      return results;
    }

    private string ExtractGlobalSection(string sectionName)
    {
      Regex sectionStartPattern = new Regex(string.Format(@"^GlobalSection\({0}\) = preSolution$", sectionName));
      Regex sectionEndPattern = new Regex("^EndGlobalSection$");

      return ExtractSectionContents(sectionStartPattern, sectionEndPattern);
    }

    private string ExtractProjectSection(string projectGuid)
    {
      Regex sectionStartPattern = new Regex(string.Format("^Project.*\"{0}\"", projectGuid));
      Regex sectionEndPattern = new Regex("^EndProject$");

      return ExtractSectionContents(sectionStartPattern, sectionEndPattern);
    }

    private string ExtractSectionContents(Regex sectionStartPattern, Regex sectionEndPattern)
    {
      using (TrimmingTextReader reader = new TrimmingTextReader(new StringReader(m_contents)))
      {
        reader.SkipUntil(sectionStartPattern);
        return reader.ReadUntil(sectionEndPattern);
      }
    }

    // The names of configuration sections vary with the solution version,
    // so derived classes can provide this here.
    public abstract string GetSolutionConfigurationSectionName();
  }
}
